package com.duojuhe.interceptor;

import com.duojuhe.common.config.DuoJuHeConfig;
import com.duojuhe.common.utils.dateutils.DateUtils;
import com.duojuhe.common.utils.iputil.IPUtils;
import com.duojuhe.common.utils.timestamp.TimeStampUtil;
import com.duojuhe.security.xss.XssHttpServletRequestWrapper;
import com.duojuhe.common.annotation.SignVerify;
import com.duojuhe.common.bean.UserTokenInfoVo;
import com.duojuhe.cache.LoginUserTokenCache;
import com.duojuhe.common.constant.SystemConstants;
import com.duojuhe.common.exception.base.DuoJuHeException;
import com.duojuhe.common.result.ErrorCodes;
import com.duojuhe.common.utils.sign.SignUtils;
import com.duojuhe.common.utils.token.TokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.SortedMap;

/**
 * @date 2018/11/12
 */
@Slf4j
@Component
public class MyWebInterceptor implements HandlerInterceptor {
    @Resource
    private DuoJuHeConfig duoJuHeConfig;
    @Resource
    private LoginUserTokenCache loginUserTokenCache;
    //演示站点禁止请求的url
    private final static List<String> NOT_REQUEST_URL_LIST = Arrays.asList(
            "/sysCommon/currentLoginUser/changeCurrentLoginUserPassword",
            "/sysAdmin/systemTenant/resetSystemTenantUserPassword",
            "/sysAdmin/systemTenant/updateSystemTenantStatusByTenantId",
            "/sysAdmin/systemUser/deleteSystemUserByUserId",
            "/sysAdmin/systemUser/resetSystemUserPassword");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
        if (RequestMethod.OPTIONS.name().equals(request.getMethod().toUpperCase())) {
            response.setStatus(HttpStatus.OK.value());
            return false;
        }
        //获取请求接口
        String reqUrl = request.getRequestURI();
        if (NOT_REQUEST_URL_LIST.contains(reqUrl)) {
            throw new DuoJuHeException("大佬您好，演示系统禁止该操作，如需测试，请下载后自行测试，谢谢!");
        }
        //获取当前登录人
        UserTokenInfoVo currentUser = LoginUserTokenCache.getCurrentLoginUserInfoDTO();
        //检查签名验证
        checkSignVerify(currentUser, request, handler);
        //自动延长token值
        refreshUserTokenExpiresTime(currentUser, request);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }

    /**
     * 刷新token登录时间
     *
     * @param currentUser
     */
    private void refreshUserTokenExpiresTime(UserTokenInfoVo currentUser, HttpServletRequest request) {
        if (!SystemConstants.UNKNOWN_ID.equals(currentUser.getUserId())) {
            //检查请求ip是否和登录时ip一致
            checkRequestIpAndLoginIp(currentUser, request);
            //token过期时间
            Integer tokenExpiresIn = duoJuHeConfig.getTokenExpiresIn();
            //相差分钟数
            long differenceMinute = DateUtils.differenceMinute(currentUser.getLoginTime(), new Date());
            //更新登录缓存，如果token 初始登录时间 与当前时间差值大于1分钟则再刷新
            if (differenceMinute >= 1) {
                loginUserTokenCache.refreshUserTokenInfoVoCacheByToken(currentUser.getToken(), tokenExpiresIn);
            }
        }
    }


    /**
     * 检查请求ip是否和登录时ip一致
     *
     * @param currentUser
     * @param request
     */
    private void checkRequestIpAndLoginIp(UserTokenInfoVo currentUser, HttpServletRequest request) {
        //获取ip当前客户
        String requestIp = IPUtils.getClientIPByIpHeader(request, duoJuHeConfig.getGetAdminIpHeader());
        if (!StringUtils.equals(requestIp, currentUser.getLoginIp())) {
            loginUserTokenCache.delUserTokenInfoVoCacheByToken(currentUser.getToken());
            throw new DuoJuHeException(ErrorCodes.LOGIN_IP_AND_REQUEST_IP_ERROR);
        }
    }


    /**
     * 检查签名验证
     *
     * @param currentUser
     * @param request
     * @param handler
     */
    private void checkSignVerify(UserTokenInfoVo currentUser, HttpServletRequest request, Object handler) {
        //当前签名统一验证放到拦截器中值
        boolean isHandlerMethod = handler.getClass().isAssignableFrom(HandlerMethod.class);
        if (isHandlerMethod) {
            //签名验证
            SignVerify signVerify = ((HandlerMethod) handler).getMethod().getAnnotation(SignVerify.class);
            if (signVerify != null && signVerify.verify()) {
                //请求地址
                String reqUrl = request.getRequestURI();
                //把request请求流中的数据复制一份出来计算sign
                XssHttpServletRequestWrapper requestWrapper = new XssHttpServletRequestWrapper(request, true);
                //取出请求体内容
                String body = requestWrapper.getBodyString();
                //签名
                String sign = SignUtils.getSignByRequest(request);
                //请求时间戳
                String timestamp = TimeStampUtil.getTimestampByRequest(request);
                //需要验证sign
                SortedMap<String, Object> map = SignUtils.jsonToSortMap(body);
                //token和timestamp需要参与签名排序
                map.put(TokenUtils.TOKEN_HEADER, currentUser.getToken());
                map.put(TimeStampUtil.TIME_STAMP_HEADER, timestamp);
                //辅助签名秘钥
                String signKey = currentUser.getSignKey();
                //签名值
                String mySign = SignUtils.getSignStr(map, signKey);
                if (!StringUtils.equals(mySign, sign)) {
                    log.error("签名错误，传入签名：{}，生成签名： {}，reqUrl： {}", sign, mySign, reqUrl);
                    throw new DuoJuHeException(ErrorCodes.SIGN_ERROR);
                }
            }
        }
    }
}
